package com.yeskery.nut.aop.handler;

import com.yeskery.nut.annotation.aop.Around;
import com.yeskery.nut.annotation.aop.Compose;
import com.yeskery.nut.aop.AroundExecution;
import com.yeskery.nut.aop.AroundProxyMethodHandlerExecutionChain;
import com.yeskery.nut.aop.Invocation;
import com.yeskery.nut.bean.ApplicationContext;
import com.yeskery.nut.core.Order;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

/**
 * Around注解的方法处理器
 * @author sprout
 * @version 1.0
 * 2023-04-08 23:22
 */
public class AroundProxyMethodHandler extends AspectProxyMethodHandler implements ComposeProxyMethodHandler {

    /**
     * 构建Around注解的方法处理器
     * @param applicationContext 应用上下文
     */
    public AroundProxyMethodHandler(ApplicationContext applicationContext) {
        super(applicationContext);
    }

    @Override
    public boolean support(Method method) {
        return getProxyMethodAspectCacheMap().containsKey(method.toString()) || method.isAnnotationPresent(Around.class)
                || support(method, c -> c.aroundArray().length > 0);
    }

    @Override
    public void around(Method method, Invocation invocation, Object target) throws Exception {
        List<ProxyMethodTarget> proxyMethodTargetList = new ArrayList<>();
        Around aroundAnnotation = method.getAnnotation(Around.class);
        if (aroundAnnotation != null) {
            proxyMethodTargetList.add(new ProxyMethodTarget(aroundAnnotation.beanName(), aroundAnnotation.beanType(),
                    aroundAnnotation.method(), aroundAnnotation.order()));
        }
        Compose compose = getComposeAnnotation(method);
        if (compose != null) {
            for (Around around : compose.aroundArray()) {
                proxyMethodTargetList.add(new ProxyMethodTarget(around.beanName(), around.beanType(),
                        around.method(), around.order()));
            }
        }

        appendAspectProxyTarget(method, Around.class, proxyMethodTargetList);
        proxyMethodTargetList.sort(Comparator.comparing(Order::getOrder));
        List<AroundExecution> aroundExecutionList = new ArrayList<>(proxyMethodTargetList.size());
        for (ProxyMethodTarget proxyMethodTarget : proxyMethodTargetList) {
            AroundExecution aroundExecution = new AroundExecution();
            aroundExecution.setProxyMethodTarget(proxyMethodTarget);
            Object object = getTargetObject(target, proxyMethodTarget.getBeanName(), proxyMethodTarget.getBeanType());
            aroundExecution.setTarget(object);
            Method invokeMethod;
            try {
                invokeMethod = object.getClass().getMethod(proxyMethodTarget.getMethodName(), Invocation.class);
                aroundExecution.setMethod(invokeMethod);
            } catch (NoSuchMethodException e) {
                invokeMethod = object.getClass().getMethod(proxyMethodTarget.getMethodName());
                aroundExecution.setMethod(invokeMethod);
            }
            aroundExecutionList.add(aroundExecution);
        }

        Invocation aroundInvocationExecutionChain = new AroundProxyMethodHandlerExecutionChain(aroundExecutionList, invocation);
        aroundInvocationExecutionChain.invoke();
    }
}
